範疇有兩種模型:
語彙範疇(lexical scope)是在lexing time時期所定義的範疇。
換句話說,語彙範疇(lexical scope)是基於撰寫程式碼與程式區塊的實際位置所定義出來的。
example
function foo(a) {
var b = a * 2;
function bar(c) {
console.log(a, b, c);
}
bar(b * 3);
}
foo(2); // 2 4 12
這個範例,有3個巢狀範疇(nested scopes):
圖片來源:You Don't Know JS: Scope & Closures
foo
。foo
的範疇,裡面包含參數a
,區域變數b
,函式bar
。bar
的範疇,裡面包含參數c
。上面的圖並不是文氏圖(Venn diagram)。函式是無法同時存在於2個同層級的範疇。
上面範例的執行過程:
foo(2);
,接著執行bar(b * 3);
,最後執行console.log(a, b, c);
。a
,b
,c
執行RHS,所以Engine會從最內層的範疇開始搜尋。a
,b
不在bar
的範疇中,所以Engine會往外層的範疇(foo
)找,並且找到。bar
的同時,b * 3
的運算結果,作為引數,傳給c
。Look-ups的動作,會在找到第一個符合的時候,就停止。
如果相同的識別子(identifier)名稱,分別定義在不同層級的範疇,會發生shadowing的情況。
「shadowing」意思是,內層範疇的識別子遮住了外層範疇同名的識別子(identifier),換句話說,只要在當時執行的範疇中找到符合的識別子(identifier),Look-ups的動作就會停止,不管外層是否有同名的識別子(identifier)都不會採用。
無論是否有shadowing的情況。Engine執行Look-ups的動作,都會從當下的範疇開始執行,視情況再往外層推移。
全域變數(global variables)本身會成為全域物件(global object)window
的屬性。
我們可以藉由此特性,來存取被遮蔽的全域變數。
全域變數被遮蔽:
var c = 10;
function foo(a) {
var b = a * 2;
var c = 5;
function bar(c) {
console.log(a, b, c);
}
bar(c);
}
foo(2); //2 4 5
使用全域物件取得屬性值:
var c = 10;
function foo(a) {
var b = a * 2;
var c = 5;
function bar(c) {
console.log(a, b, c);
}
bar(window.c);
}
foo(2); //2 4 10
但如果被遮蔽的是非全域變數(non-global)的話,就無法使用此方法。
不管在何處呼叫函式,也不管是如何呼叫的,該函式在宣告(declaration)的時候,就已經決定其語彙範疇(lexical scope)了,不會改變。
語彙範疇(Lexical scope)在宣告函式的時候就已經定義好了,也就是實際撰寫程式碼的位置。
不要使用eval & with。
此為You Don't Know JS系列的筆記。